/*
 * tsock.c
 *
 *  Created on: 2011-08-16
 *      Author: Baron Wang
 */

#include <stdlib.h>
#include <memory.h>
#include <ctype.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/uio.h>
#include <errno.h>
#ifdef _SCTP
#include <netinet/sctp.h>
#endif

#include "tsock.h"
#include "tlog.h"

#define LOG_UNSPEC(_type) LOG(_CRI, "an unsupported IP type[%d].", _type)

const char* prtl_to_str(const int protocol){
    switch(protocol){
        case SYS_UDP:
            return "UDP";
        case SYS_TCP:
            return "TCP";
        case SYS_SCTP:
            return "SCTP";
        case SYS_INVALID:
            return "INVALID";
        default:
            return "UNSPEC";
    }
}

const char* type_to_str(const int type){
    switch(type){
        case AF_UNSPEC:
            return "UNSPEC";
        case AF_INET:
            return "IPv4";
        case AF_INET6:
            return "IPv6";
        case AF_UNIX:
            return "UNIX";
        default:
            return "INVALID";
    }
}

#define ALLOC_TO_CONVERTEP(_buf, _ep)                                    \
do{                                                                      \
    _buf = (char*)malloc(INET6_ADDRSTRLEN + 1);                          \
    if(NULL == _buf){                                                    \
        LOG(_CRI, "failed to alloc memory.");                            \
        return -1;                                                       \
    }                                                                    \
    ep_to_addr(_ep, _buf, INET6_ADDRSTRLEN + 1);                         \
    _buf[INET6_ADDRSTRLEN] = '\0';                                       \
}while(0)

int sockaddr_to_ep(const struct sockaddr *addr, int protocol, tsock_ep *ep){

    CHECKNULL(addr, return -1)
    CHECKNULL(ep, return -1)

    switch(addr->sa_family){
        case AF_INET:
            ep->type = AF_INET;
            ep->prtl = protocol;
            ep->port = ((struct sockaddr_in *)addr)->sin_port;
            memcpy(&ep->v4addr, &((struct sockaddr_in *)addr)->sin_addr,
                sizeof(struct in_addr));
        return 0;

        case AF_INET6:
            ep->type = AF_INET6;
            ep->prtl = protocol;
            ep->port = ((struct sockaddr_in6 *)addr)->sin6_port;
            memcpy(&ep->v6addr, &((struct sockaddr_in6 *)addr)->sin6_addr,
                sizeof(struct in6_addr));
        return 0;

        default:
            LOG_UNSPEC(addr->sa_family);
        return -1;
    }
}

/* suppose the passing parameter is all valid */
static __inline int __sockep_to_sockaddr4(const tsock_ep *endpoint,
    struct sockaddr_in *v4){

    bzero(v4, sizeof(struct sockaddr_in));
    v4->sin_port = endpoint->port;
    v4->sin_family = endpoint->type;
    memcpy(&v4->sin_addr, &endpoint->v4addr, sizeof(struct in_addr));
    return sizeof(struct sockaddr_in);
}

/* suppose the passing parameter is all valid */
static __inline int __sockep_to_sockaddr6(const tsock_ep *endpoint,
    struct sockaddr_in6 *v6){

    bzero(v6, sizeof(struct sockaddr_in6));
    v6->sin6_port = endpoint->port;
    v6->sin6_family = endpoint->type;
    memcpy(&v6->sin6_addr, &endpoint->v6addr, sizeof(struct in6_addr));
    return sizeof(struct sockaddr_in6);
}

static __inline int __ip_type(const char* addr_sz){
    /* first, we need to determine if it is a IPv4 or IPv6 addr     */
    /* set it to 1 is because a, a.b, a.b.c, a.b.c.d is all valid     */
    register int dot_num = 1;
    char* colon = NULL;
    int have_no_digit = 0;

    register char *p = (char*)addr_sz;

    while(*p != '\0'){

        if(*p == '.'){
            ++dot_num;
        }
        else if(*p == ':'){
            colon = p;
            /* must not a IPv4 */
            break;
        }
        else{
            /* find a non-digit value, it must not be a IPv4 */
            if(0 == have_no_digit){
                if((*p & 0x30) != 0x30){
                    have_no_digit = 1;
                }
            }
        }
        ++p;
    }

    if(!colon){
        if(dot_num && !have_no_digit){
            return AF_INET;
        }
        else{
            return AF_MAX;
        }

    }else{
        return AF_INET6;
    }
}

void reset_ep(IN tsock_ep *endpoint){
    if(endpoint){
        endpoint->prtl = SYS_INVALID;
    }
}

int is_ep_init(IN const tsock_ep *endpoint){
    if(endpoint){
        return SYS_INVALID != endpoint->prtl;
    }
    return 0;
}

int is_multicast(IN const tsock_ep *endpoint){


    CHECKNULL(endpoint, return 0)

    switch(endpoint->type){
        case AF_INET:
            return IN_CLASSD(ntohl(endpoint->v4addr.s_addr));
        case AF_INET6:
            return IN6_IS_ADDR_MULTICAST(&(endpoint->v6addr));
            break;
        default:
            return 0;
    }
}

int addr_to_ep(const char* addr_sz, int protocol, in_port_t port,
    tsock_ep *endpoint){

    register int type;

    CHECKNULL(addr_sz, return -1);
    CHECKNULL(endpoint, return -1);

    type = __ip_type(addr_sz);

    switch(type){
        case AF_INET:
            /* usually, inet_aton has better performance     */
            /* reuse register dot_num for return value        */
            if(0 == inet_aton(addr_sz, &endpoint->v4addr)){
                LOG(_CRI, "failed to convert an IPv4 socket(%s),"
                    "reason[%d](%s).", addr_sz, errno, strerror(errno));
                return -1;
            }
            endpoint->type = AF_INET;
            endpoint->port = htons(port);
            endpoint->prtl = protocol;
            break;
        case AF_INET6:
            /* should be a IPv6 address, check it */
            /* reuse the dot_num to save the return value */
            if(inet_pton(AF_INET6, addr_sz, &endpoint->v6addr) <= 0){
                LOG(_CRI, "failed to convert an IPv6 socket(%s),"
                    "reason[%d](%s).", addr_sz, errno, strerror(errno));
                return -1;
            }
            endpoint->type = AF_INET6;
            endpoint->port = htons(port);
            endpoint->prtl = protocol;

            break;
        case AF_MAX:
        default:
            LOG(_CRI, "failed to convert a socket(%s) because it is "
                "not an IPv4 or IPv6 format.", addr_sz);
            return -1;
            break;
    }

    return 0;
}

const char* ep_to_addr(const tsock_ep *endpoint, char* buf, int size){

    register const char* res = NULL;

    CHECKNULL(endpoint, return "")
    CHECKNULL(buf, return "")

    if(size <= 0){
        LOG(_ASS, "passing an error length[%d] for buffer", size);
        return "";
    }

    /* please note, inet_ntoa is not a thread safe function */
    switch(endpoint->type){
        case AF_INET:
            if(size < INET_ADDRSTRLEN){
                LOG(_CRI, "passing too small a buffer, size[%d].", size);
                return "";
            }
            res = inet_ntop(AF_INET, &endpoint->v4addr, buf, INET_ADDRSTRLEN);

            if(NULL == res){
                LOG(_CRI, "fail to convert IPv4[%X] to addr, reason[%d](%s).",
                    endpoint->v4addr.s_addr, errno, strerror(errno));
                return "";
            }
        break;

        case AF_INET6:
            if(size < INET6_ADDRSTRLEN){
                LOG(_CRI, "passing too small a buffer, size[%d].", size);
                return "";
            }
            res = inet_ntop(AF_INET6, &endpoint->v6addr, buf,
                INET6_ADDRSTRLEN);
            if(NULL == res){
                LOG(_CRI, "fail to convert IPv6 to addr, reason[%d](%s).",
                    errno, strerror(errno));
                return "";
            }
        break;

        default:
            buf[0] = '\0';
            LOG_UNSPEC(endpoint->type);
            return "";
        break;
    }

    return res;
}

int t_socket(const tsock_ep *endpoint){

    int type;
    int protocol;
    int sockfd;
    char *logbuf;    /* use a pointer to reduce the stack size */

    CHECKNULL(endpoint, return -1);

    protocol = endpoint->prtl;

    switch(protocol){
        case SYS_TCP:
            type = SOCK_STREAM;
            break;
        case SYS_UDP:
            type = SOCK_DGRAM;
            break;
#ifdef _SCTP
        case SYS_SCTP:
            /* we are initing a one-to-many style interface */
            type = SOCK_SEQPACKET;
            break;
#endif
        case SYS_INVALID:
        default:
            LOG(_CRI, "unsupported endpoint protocol[%d].", endpoint->prtl);
            return -1;
    }

    sockfd = socket(endpoint->type, type, protocol);

    if(sockfd < 0){
        ALLOC_TO_CONVERTEP(logbuf, endpoint);
        LOG(_CRI, "error creating socket IP(%s), type(%s), protocol(%s),"
            " port[%d](not used). return code %d, reason[%d](%s).",
            logbuf,
            type_to_str(endpoint->type),
            prtl_to_str(protocol),
            ntohs(endpoint->port),
            sockfd,
            errno,
            strerror(errno));
        free(logbuf);
        return -1;
    }
    return sockfd;
}

int t_bind(int sockfd, const tsock_ep *endpoint){

    t_sockaddr addr;
    struct sockaddr *s_ptr = NULL;

    int len = 0;
    int res;
    char *logbuf;    /* use a pointer to reduce the stack size */

    CHECKNULL(endpoint, return -1);

    switch(endpoint->type){
        case AF_INET:
            (void)__sockep_to_sockaddr4(endpoint, &addr.sa_in);
            len = sizeof(struct sockaddr_in);
            s_ptr = &addr.sa;
            break;
        case AF_INET6:
            (void)__sockep_to_sockaddr6(endpoint, &addr.sa_in6);
            len = sizeof(struct sockaddr_in6);
            s_ptr = &addr.sa;
            break;
        default:
            LOG_UNSPEC(endpoint->type);
            return -1;
    }

#ifdef _SCTP
    if(SYS_SCTP == endpoint->protocol){
        /*
         * please note, the socket file descriptor can be
         * binded with multiple address, just call sys_bind,
         * it is reenterable.
         */
        res = sctp_bindx(sockfd, s_ptr, 1, SCTP_BINDX_ADD_ADDR);
    }else{
#endif
    /* Bind a TCP or UDP socket, either v4 or v6 */
    res = bind(sockfd, s_ptr, len);
#ifdef _SCTP
    }
#endif
    if(res < 0){
        ALLOC_TO_CONVERTEP(logbuf, endpoint);
        LOG(_CRI, "bind failed for socket IP(%s), type(%s), protocol(%s),"
            " port[%d] in socked fd[%d], result is %d. reason[%d](%s).",
            logbuf,
            type_to_str(endpoint->type),
            prtl_to_str(endpoint->prtl),
            ntohs(endpoint->port),
            sockfd,
            res,
            errno,
            strerror(errno));
        free(logbuf);
        return -1;
    }

    return 0;
}

__inline int t_listen(int sockfd){
    return listen(sockfd, 5);
}

/*
 * This is a very complicate function, that for TCP and UDP, num is always 1
 * (UDP can use it as a reenterate function to change its associates)
 * For SCTP, endpoint is an arry passed in and it's connecting multi-socket.
 */
int t_connect(int sockfd, const tsock_ep *ep, int num, int *err){

    int protocol;
    int len;
    int res;
    char *logbuf;

    t_sockaddr addr;
    struct sockaddr *s_ptr = NULL;

#ifdef _SCTP
    int cnt = num;
    struct sockaddr_storage* sctp_addr;
    register char *p;
#endif

    CHECKNULL(ep, return -1);
    protocol = ep[0].prtl;

    if(SYS_TCP == protocol || SYS_UDP == protocol){


        switch(ep->type){
            case AF_INET:
                (void)__sockep_to_sockaddr4(ep, &addr.sa_in);
                len = sizeof(struct sockaddr_in);
                s_ptr = &addr.sa;
                break;
            case AF_INET6:
                (void)__sockep_to_sockaddr6(ep, &addr.sa_in6);
                len = sizeof(struct sockaddr_in6);
                s_ptr = &addr.sa;
                break;
            default:
                LOG_UNSPEC(ep->type);
                return -1;
        }

        res = connect(sockfd, s_ptr, len);
        if(res < 0){
            *err = errno;
            ALLOC_TO_CONVERTEP(logbuf, ep);

            LOG(_CRI, "connect failed for socket IP(%s), type(%s), "
                "protocol(%s), port[%d] in socked fd[%d], return code[%d]. "
                "reason[%d](%s).",
                logbuf,
                type_to_str(ep->type),
                prtl_to_str(ep->prtl),
                ntohs(ep->port),
                sockfd,
                res,
                errno,
                strerror(errno));
            free(logbuf);
            return -1;
        }

        /* A RTM log for the accept function to indicate a new connect */
        CHKLOGLVL(L_RTM){

            ALLOC_TO_CONVERTEP(logbuf, ep);

            LOG(_RTM, "establish a connection for socket[%d], remote "
                "address(%s), port[%d], AF_TYPE(%s), protocol(%s).",
                sockfd, logbuf, ntohs(ep->port),
                type_to_str(ep->type),
                prtl_to_str(ep->prtl));

            free(logbuf);
        }

        return 0;

    }else{

#ifdef _SCTP
        /* it is a SCTP, then, try loop */
        sctp_addr = (struct sockaddr_storage*)malloc(
            sizeof(struct sockaddr_storage) * num);

        p = (char*)sctp_addr;

        CHECKNULL(sctp_addr, return -1);

        while(cnt > 0){

            switch(ep->type){
                case AF_INET:
                    p += __sockep_to_sockaddr4(ep, (struct sockaddr_in *)p);
                    break;
                case AF_INET6:
                    p += __sockep_to_sockaddr6(ep, (struct sockaddr_in6 *)p);
                    break;
                default:
                    LOG_UNSPEC(ep->type);
                    free(sctp_addr);
                    return -1;
            }
            --cnt;
            ep++;
        }
        res = sctp_connectx(sockfd, (struct sockaddr *)sctp_addr, num);
        free(sctp_addr);
        if(res < 0){

            ALLOC_TO_CONVERTEP(logbuf, ep);
            /* ep is the current failed ep */
            LOG(_CRI, "connect failed for socket IP(%s), type(%s),"
                " protocol(%s), port(%d) in socked fd[%d], return code[%d]."
                " reason[%d](%s).",
                logbuf,
                type_to_str(ep->type),
                prtl_to_str(ep->prtl),
                ntohs(ep->port),
                sockfd,
                res,
                errno,
                strerror(errno));

            free(logbuf);
            return -1;
        }
        return 0
#else
        LOG(_CRI, "do NOT support SCTP yet.");
        return -1;
#endif
    }
}

int t_tcp_accept(int sockfd, tsock_ep* ep){

    int newfd;
    socklen_t len = sizeof(struct sockaddr_storage);
    t_sockaddr ssa;
    char* logbuf;

    CHECKNULL(ep, return -1);

    newfd = accept(sockfd, &ssa.sa, &len);

    if(newfd < 0){
        LOG(_CRI, "failed to accept connection for socket[%d],"
            " return code[%d], error[%d](%s).",
            sockfd, newfd, errno, strerror(errno));
        return -1;
    }

    switch(ssa.sa.sa_family){
        case AF_INET:
            ep->type = AF_INET;
            ep->prtl = SYS_TCP;
            ep->port = ssa.sa_in.sin_port;
            memcpy(&ep->v4addr, &ssa.sa_in.sin_addr, sizeof(struct in_addr));
            break;
        case AF_INET6:
            ep->type = AF_INET6;
            ep->prtl = SYS_TCP;
            ep->port = ssa.sa_in6.sin6_port;
            memcpy(&ep->v6addr, &ssa.sa_in6.sin6_addr,
                sizeof(struct in6_addr));
            break;
        default:
            LOG_UNSPEC(ssa.sa.sa_family);
            close(newfd);
            return -1;
    }

    /* A RTM log for the accept function to indicate a passive connect */
    CHKLOGLVL(L_RTM){

        ALLOC_TO_CONVERTEP(logbuf, ep);

        LOG(_RTM, "accept a TCP connection for socket[%d], "
            "from remote peeraddress(%s), port[%d], AF_TYPE(%s), the new "
            "socket allocated is[%d].",
            sockfd, logbuf, ntohs(ep->port),
            type_to_str(ep->type),
            newfd);
        free(logbuf);
    }

    return newfd;
}

int t_sctp_accept(int sockfd, tsock_ep* ep, int *num){
#ifdef _SCTP

    struct msghdr rmsg;
    struct msghdr *p;
    struct iovec iov; /* the receive buffer */
    struct sockaddr_in addrs[4];
    char buff[256];
    int res;
    int newfd;

    sctp_cmsg_data_t cmsg;
    struct sctp_assoc_change *sac;
    union sctp_notification *nmsg;


    CHECKNULL(ep, return -1);
    iov.iov_base = buff, iov.iov_len = sizeof(buff);


    rmsg.msg_iov = &iov;
    rmsg.msg_iovlen = 1;
    rmsg.msg_flags = 0;
    rmsg.msg_control = (char *)&cmsg;
    rmsg.msg_controllen = sizeof(cmsg);
    rmsg.msg_name = (void*)&addrs[0];
    rmsg.msg_namelen = sizeof(addrs[0]);

retry:
    res = recvmsg(sockfd, &rmsg, MSG_NOSIGNAL);
    if(res < 0){
        if(EINTR == errno){
            /* Interrupted call */
            goto retry;
        }
        LOG(_CRI, "recvmsg failed, socket[%d], res[%d](%s).",
            sockfd, res, STD_ERROR_TO_STR);
        return res;
    }else if(0 == res){
        LOG(_CRI, "recvmsg nothing, socket[%d].", sockfd);
        return TRANS_FAILED;
    }

    p = &rmsg;
    if(0 == (p->msg_flags & MSG_NOTIFICATION)){
        LOG(_CRI, "not recv a notification msg.");
        return TRANS_FAILED;
    }
    nmsg = (union sctp_notification *) p->msg_iov->iov_base;

    if(SCTP_ASSOC_CHANGE != nmsg->sn_header.sn_type){
        LOG(_CRI, "unexpected Notification type[%d]"
            " while Listening on SCTP socket.", nmsg->sn_header.sn_type);
        return TRANS_FAILED;
    }

    sac = &nmsg->sn_assoc_change;
    if(SCTP_COMM_UP == sac->sac_state){
        newfd = sys_sctp_peeloff(sockfd, sac->sac_assoc_id, ep, num);
    }else{
        LOG(_CRI, "unknown sac_state[%d].", sac->sac_state);
        return TRANS_FAILED;
    }

    return newfd;

#else
    return -1;
#endif
}

int t_sctp_peeloff(int sockfd, int aid, tsock_ep *ep, int *num){

#ifdef _SCTP

    struct sockaddr_storage p_addr;
    int newfd;
    int res;
    struct sockaddr *paddrs;
    struct sockaddr *p;

    CHECKNULL(ep, return -1);
    memset(&p_addr, 0, sizeof(struct sockaddr_storage));

    newfd = sctp_peeloff(sockfd, aid);

    if(newfd < 0){
        LOG(_CRI, "fail to peeloff socket [%d],"
            "ass id[%d], reason[%d](%s)",
            sockfd, aid, newfd, STD_ERROR_TO_STR);
        return TRANS_FAILED;
    }

    res = sctp_getpaddrs(newfd, aid, &paddrs);
    if(res < 0){
        LOG(_CRI, "fail to get socket addr for socket[%d], aid[%d] "
            "reason[%d](%s)", newfd, aid, res, STD_ERROR_TO_STR);
        return TRANS_FAILED;
    }

    if(res > *num){
        LOG(LOG_ERR, "more num[%d] in addr than buffer[%d]", res, *num);
        return TRANS_FAILED;
    }

    *num = res;
    /* use paddrs_itor to release the resource */
    p = paddrs;
    while(0 != res){

        switch(p->sa_family){

            case AF_INET:
                ep->type = AF_INET;
                ep->prtl = SYS_SCTP;
                ep->port = ((struct sockaddr_in *)p)->sin_port;
                memcpy(&ep->v4addr, &((struct sockaddr_in *)p)->sin_addr,
                    sizeof(struct in_addr));

                p = (struct sockaddr *)((char*)p + sizeof(struct sockaddr_in));
            break;

            case AF_INET6:
                ep->type = AF_INET6;
                ep->prtl = SYS_SCTP;
                ep->port = ((struct sockaddr_in6 *)p)->sin6_port;
                memcpy(&ep->v6addr, &((struct sockaddr_in6 *)p)->sin6_addr,
                    sizeof(struct in6_addr));

                p = (struct sockaddr *)((char*)p +
                                sizeof(struct sockaddr_in6));
            break;

            default:
                LOG_UNSPEC(p->sa_family);
                res = TRANS_UNSUPPORTED;
                goto out;
            break;
        }
        ++ep;
        --res;
    }

out:
    sctp_freepaddrs(paddrs);
    return res < 0 ? res : newfd;
#else
    return -1;
#endif
}

__inline int t_close(int fd){
    LOG(_RTM, "socked[%d] closed.", fd);
    return close(fd);
}

__inline ssize_t t_send(int s, const void *buf, size_t len, int flags){
    return send(s, buf, len, flags);
}

__inline ssize_t t_recv(int s, void *buf, size_t len, int flags){
    return recv(s, buf, len, flags);
}

ssize_t t_sendto(int sockfd, const void *msg, unsigned int len,
    int flags, const tsock_ep *to){

    struct sockaddr_in v4a;
    struct sockaddr_in6 v6a;

    CHECKNULL(to, return -1);
    CHECKNULL(msg, return -1);

    if(len <= 0){
        LOG(_ASS, "passing an error msg len[%d] when sendto.", len);
    }

    switch(to->type){
        case AF_INET:

            (void)__sockep_to_sockaddr4(to, &v4a);
            return sendto(sockfd, msg, len, flags,
                (struct sockaddr *)&v4a,
                sizeof(struct sockaddr_in));
        case AF_INET6:
            (void)__sockep_to_sockaddr6(to, &v6a);
            return sendto(sockfd, msg, len, flags,
                (struct sockaddr *)&v6a,
                sizeof(struct sockaddr_in6));
        default:
            LOG_UNSPEC(to->type);
            return -1;
    }
}


ssize_t t_recvfm(int sockfd, void *buf, unsigned int len, int flags,
    tsock_ep *from){

    struct sockaddr_storage ss;

    ssize_t res;
    socklen_t slen = sizeof(struct sockaddr_storage);

    CHECKNULL(from, return -1);
    CHECKNULL(buf, return -1);

    if(len <= 0){
        LOG(_ASS, "passing an invalid buffer lenth[%d].", len);
        return -1;
    }

    res = recvfrom(sockfd, buf, len, flags, (struct sockaddr *)&ss, &slen);
    if(res < 0){
        LOG(_CRI, "fail to recv from socket[%d], reason[%d](%s).",
            sockfd, res, strerror(errno));
        return -1;
    }

    if(sockaddr_to_ep((struct sockaddr *)&ss, SYS_UDP, from)){
        return -1;
    }

    return res;

}

